/* ====================================================================
|
|   Jtalk Smalltalk
|   http://jtalk-project.org
|
 ======================================================================

 ======================================================================
|
| Copyright (c) 2010-2011
| Nicolas Petton <petton.nicolas@gmail.com>
|
| Jtalk is released under the MIT license
|
| Permission is hereby granted, free of charge, to any person obtaining
| a copy of this software and associated documentation files (the 
| 'Software'), to deal in the Software without restriction, including 
| without limitation the rights to use, copy, modify, merge, publish, 
| distribute, sublicense, and/or sell copies of the Software, and to 
| permit persons to whom the Software is furnished to do so, subject to 
| the following conditions:
|
| The above copyright notice and this permission notice shall be 
| included in all copies or substantial portions of the Software.
|
| THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
| IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
| CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
| TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
| SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  
|
 ==================================================================== */


/* Smalltalk constructors definition */

function Smalltalk(){};
function SmalltalkObject(){};
function SmalltalkBehavior(){};
function SmalltalkClass(){};
function SmalltalkMetaclass(){
    this.meta = true;
};
function SmalltalkMethod(){};
function SmalltalkNil(){};

/* Global Smalltalk objects. nil shouldn't be a global. */

var nil = new SmalltalkNil();
var smalltalk = new Smalltalk();


/* Smalltalk class creation. A class is an instance of an automatically 
   created metaclass object. Newly created classes (not their metaclass) 
   should be added to the smalltalk object, see smalltalk.addClass().
   Superclass linking is *not* handled here, see smalltalk.init()  */

smalltalk.klass = function(spec) {
    var spec = spec || {};
    var that;
    if(spec.meta) {
	that = new SmalltalkMetaclass();
    } else {
	that = new (smalltalk.klass({meta: true})).fn;
	that.klass.instanceClass = that;
	that.className = spec.className;
	that.klass.className = that.className + ' class';
    }
	
    that.fn = spec.fn || function(){};
    that.superclass = spec.superclass;
    that.iVarNames = spec.iVarNames || [];
    if(that.superclass) {
	that.klass.superclass = that.superclass.klass;
    }
    that.category = spec.category || "";
    that.fn.prototype.methods = {};
    that.fn.prototype.klass = that;

    return that;
};

/* Smalltalk method object. To add a method to a class,
   use smalltalk.addMethod() */

smalltalk.method = function(spec) {
    var that = new SmalltalkMethod();
    that.selector = spec.selector;
    that.category = spec.category;
    that.source   = spec.source;
    that.fn       = spec.fn;
    return that
};

/* Initialize a class in its class hierarchy. Handle both class and
   metaclasses. */

smalltalk.init = function(klass) {
    var subclasses = smalltalk.subclasses(klass);
    for(var i=0;i<klass.iVarNames.length;i++) {
	klass.fn.prototype["@"+klass.iVarNames[i]] = nil;
    }
    if(klass.superclass && klass.superclass !== nil) {
	klass.fn.prototype.__proto__ = klass.superclass.fn.prototype;
	for(var i=0;i<klass.superclass.iVarNames.length;i++) {
	    if(!klass["@"+klass.superclass.iVarNames[i]]) {
		klass.fn.prototype["@"+klass.superclass.iVarNames[i]] = nil;
	    }
	}
    }
    for(var i=0;i<subclasses.length;i++) {
     	smalltalk.init(subclasses[i]);
    }
    if(klass.klass && !klass.meta) {
	smalltalk.init(klass.klass);
    }
};

/* Answer all registered Smalltalk classes */

smalltalk.classes = function() {
    var classes = [];
    for(var i in smalltalk) {
	if(i.search(/^[A-Z]/g) != -1) {
	    classes.push(smalltalk[i]);
	}

    }
    return classes
};

/* Answer the direct subclasses of a given class.
   This is computed dynamically */

smalltalk.subclasses = function(klass) {
    var subclasses = [];
    var classes = smalltalk.classes();
    for(var i in classes) {
	if(classes[i].fn) {
	    //Metaclasses
	    if(classes[i].klass && classes[i].klass.superclass === klass) {
		subclasses.push(classes[i].klass);
	    }
	    //Classes
	    if(classes[i].superclass === klass) {
		subclasses.push(classes[i]);
	    }
	}
    }
    return subclasses;
};

/* Create a new class wrapping a JavaScript constructor, and add it to the 
   global smalltalk object. */

smalltalk.mapClassName = function(className, category, fn, superclass) {
    smalltalk[className] = smalltalk.klass({
		className:  className, 
		category:   category, 
		superclass: superclass,
		fn:         fn
	});
};

/* Add a class to the smalltalk object, creating a new one if needed. */

smalltalk.addClass = function(className, superclass, iVarNames, category) {
    if(smalltalk[className]) {
	smalltalk[className].superclass = superclass;
	smalltalk[className].iVarNames = iVarNames;
	smalltalk[className].category = category || smalltalk[className].category;
    } else {
	smalltalk[className] = smalltalk.klass({
		className: className, 
		iVarNames: iVarNames,
		superclass: superclass
	    });
	smalltalk[className].category = category || '';
    }
};

/* Add a method to a class */

smalltalk.addMethod = function(jsSelector, method, klass) {
    klass.fn.prototype[jsSelector] = method.fn;
    klass.fn.prototype.methods[method.selector] = method;
};

/* Handles Smalltalk message send. Automatically converts undefined to the nil object.
   If the receiver does not understand the selector, call its #doesNotUnderstand: method */

smalltalk.send = function(receiver, selector, args) {
    if(typeof receiver === "undefined") {
	receiver = nil;
    }
    if(receiver[selector]) {
	return receiver[selector].apply(receiver, args);
    } else {
	return smalltalk.messageNotUnderstood(receiver, selector, args);
    }
};


/* handle #dnu:. 
   Assume that the receiver understands #doesNotUnderstand: */

smalltalk.messageNotUnderstood = function(receiver, selector, args) {
    return receiver._doesNotUnderstand_(
	smalltalk.Message._new()
	    ._selector_(smalltalk.convertSelector(selector))
	    ._arguments_(args)
    );
};

/* Convert a string to a valid smalltalk selector.
   if you modify the following functions, also change String>>asSelector
   accordingly */

smalltalk.convertSelector = function(selector) {
    if(selector.match(/__/)) {
	return smalltalk.convertBinarySelector(selector);
    } else {
	return smalltalk.convertKeywordSelector(selector);
    }
};

smalltalk.convertKeywordSelector = function(selector) {
    return selector.replace(/^_/, '').replace(/_/g, ':');
};

smalltalk.convertBinarySelector = function(selector) {
    return selector
	.replace(/^_/, '')
	.replace(/_plus/, '+')
	.replace(/_minus/, '-')
	.replace(/_star/, '*')
	.replace(/_slash/, '/')
	.replace(/_gt/, '>')
	.replace(/_lt/, '<')
	.replace(/_eq/, '=')
	.replace(/_comma/, ',')
	.replace(/_at/, '@')
};


/****************************************************************************************/


/* Base classes mapping. If you edit this part, do not forget to set the superclass of the
   object metaclass to Class after the definition of Object */

smalltalk.mapClassName("Object", "Kernel", SmalltalkObject);
smalltalk.mapClassName("Smalltalk", "Kernel", Smalltalk, smalltalk.Object);
smalltalk.mapClassName("Behavior", "Kernel", SmalltalkBehavior, smalltalk.Object);
smalltalk.mapClassName("Class", "Kernel", SmalltalkClass, smalltalk.Behavior);
smalltalk.mapClassName("Metaclass", "Kernel", SmalltalkMetaclass, smalltalk.Behavior);
smalltalk.mapClassName("CompiledMethod", "Kernel", SmalltalkMethod, smalltalk.Object);

smalltalk.Object.klass.superclass = smalltalk.Class

smalltalk.mapClassName("Number", "Kernel", Number, smalltalk.Object);
smalltalk.mapClassName("BlockClosure", "Kernel", Function, smalltalk.Object);
smalltalk.mapClassName("Boolean", "Kernel", Boolean, smalltalk.Object);
smalltalk.mapClassName("Date", "Kernel", Date, smalltalk.Object);
smalltalk.mapClassName("UndefinedObject", "Kernel", SmalltalkNil, smalltalk.Object);

smalltalk.mapClassName("Collection", "Kernel", null, smalltalk.Object);
smalltalk.mapClassName("String", "Kernel", String, smalltalk.Collection);
smalltalk.mapClassName("RegularExpression", "Kernel", RegExp, smalltalk.String);
smalltalk.mapClassName("Array", "Kernel", Array, smalltalk.Collection);

if(CanvasRenderingContext2D) {
    smalltalk.mapClassName("CanvasRenderingContext", "Canvas", CanvasRenderingContext2D, smalltalk.Object);
}
